#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

static struct fb_var_screeninfo var;
static struct fb_fix_screeninfo fix;
int line_width = 0;
int pixel_witth = 0;
int screen_size = 0;
unsigned char *fb_base = NULL;

void lcd_put_pixel(void * fb_base, int x, int y, unsigned int color)
{
    unsigned char *pen_8 = fb_base+y*line_width+x*pixel_witth;
    unsigned short *pen_16;
    unsigned int *pen_32;
    unsigned int red, green, blue;
    pen_16 = (unsigned short *)pen_8;
    pen_32 = (unsigned int *)pen_8;
    switch (var.bits_per_pixel)
    {
    case 8:
        *pen_8 = color;
        break;
    case 16:
        /* 565 */
        red = (color >> 16) & 0xff;
        green = (color >> 8) & 0xff;
        blue = (color >> 0) & 0xff;
        color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
        *pen_16 = color;
        break;
    case 32:
        *pen_32 = color;
        break;
    default:
        printf("can not support %dbpp\r\n",var.bits_per_pixel);
        break;
    }
}

void lcd_draw_screen(void * fb_base, unsigned int color)
{
    int x,y;
    for (x = 0; x < var.xres; x++)
    {
        for (y = 0; y < var.yres; y++)
        {
            lcd_put_pixel(fb_base,x,y,color);
        }
        
    }
    
}

int main(int argc, char** argv)
{
    int nBuffers;
    int ret;
    int nNextBuffer = 1;
    char *pNextBuffer;
    unsigned int colors[] = {0x00FF0000,0x0000ff00,0x000000ff,0x00000000,0x00ffffff};//0x00RRGGBB
    if(argc != 2)
    {
        printf("Usage: %s <single|double>\r\n",argv[0]);
        return -1;
    }
    int fd_fb = open("/dev/fb0",O_RDWR);
    printf("fd_fb = %d\r\n",fd_fb);
    if(fd_fb < 0)
    {
        printf("can not open /dev/fb0\r\n");
        return 0;
    }
    if(ioctl(fd_fb,FBIOGET_FSCREENINFO,&fix))
    {
        printf("can not get screen fix\r\n");
        return 0;
    }
    if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&var))
    {
        printf("can not get screen var\r\n");
        return 0;
    }
    line_width = var.xres * var.bits_per_pixel / 8;
    pixel_witth = var.bits_per_pixel / 8;
    screen_size = var.xres * var.yres * var.bits_per_pixel / 8;

    nBuffers = fix.smem_len/screen_size;
    printf("nBuffers : %d\r\n",nBuffers);
    fb_base = (unsigned char *)mmap(NULL, fix.smem_len, PROT_READ | PROT_WRITE,MAP_SHARED, fd_fb, 0);
    if(fb_base == (unsigned char *)-1)
    {
        printf("can not mmap\r\n");
        return 0;
    }
    
    if (argv[1][0] == 's' || nBuffers == 1)
    {
        while (1)
        {
            /* use single buffer */
            for (size_t i = 0; i < sizeof(colors)/sizeof(colors[0]); i++)
            {
                /* code */
                lcd_draw_screen(fb_base, colors[i]);
                usleep(100000);
            }
        }
    }
    else
    {
        /* use double buffer */
        /* a. enable use multi buffers */
        var.yres_virtual = nBuffers * var.yres;
        if(ioctl(fd_fb,FBIOPUT_VSCREENINFO,&var))
        {
            printf("can not put screen var\r\n");
            return 0;
        }
        while (1)
        {
            
            for (size_t i = 0; i < sizeof(colors)/sizeof(colors[0]); i++)
            {
                /* get buffer */
                pNextBuffer = fb_base + nNextBuffer * screen_size;
                /* set buffer */
                lcd_draw_screen(pNextBuffer, colors[i]);
                /* switch buffer */
                var.yoffset = nNextBuffer * var.yres;
                ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
                ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
                nNextBuffer = !nNextBuffer;
                usleep(100000);
            }
        }
        
    }
    


    munmap(fb_base, screen_size);
    close(fd_fb);
    return 0;
}